Skip to content

refactor(metadata-protocol): generate the protocol.ts section split with a deterministic codemod - #29998

Merged
objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/project-thread-w94e86
Nov 4, 2026
Merged

objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/project-thread-w94e86

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

Requested by Yibo Wang · project thread

Part of #29985

维护者速读

  • 改了什么:codemod scripts/codemod-split-protocol.mjs 按裁决 6624345766(门面形态,方案 A)生成 protocol.ts 的拆分。ObjectStackProtocolImplementation 成为门面:85 个成员各留一行委派,方法体移入 10 个无状态 facet 类(<Module>Facet,静态方法,首参是 host);私有成员的文档块随方法体移走(54 个),公开成员的文档块留在门面;只读参数的私有静态方法也移出(7 个)。状态全部留在门面。生成文件未手改。
  • 为什么改:[Decision] Large source files: replace the hand-cut split with a script-generated split per file, so the remaining monoliths split in days, not months #29465 大文件拆分;裁决 6624258595 把目标改为门面形态,6624345766 把 .d.ts 门槛改为「公开面相等」。
  • 风险与回滚:纯搬移。85/85 方法体逐字节还原;--verify 先把门面形态逆提回 (a) 的模块形态,再逆推出 base 的 protocol.ts 和 protocol-host.ts 逐字节相同。公开面 .d.ts 相等(对照抓到)。测试 base 22,346 → head 22,347(+1 条非空检查)。回滚即 revert。
  • 先看:① 「门面读数」一节的例外清单(tooling: a structural gate that refuses any method body in a facade file once protocol.ts and engine.ts land in facade form (ruling 6624258595) #30019 门禁的初始白名单);② codemod 的 lift / unlift;③ check-durability-degradation-log-level.mjs 对 <X>Facet.fn(...) 存根的跟随和 3 条自测。
  • 两分钟验证:pnpm install --frozen-lockfile 后运行 node scripts/codemod-split-protocol.mjs --verify --base eee41490,预期 85/85 moved bodies restore to the base body exactly、23135 -> 13647 lines,退出码 0;--self-test 55 例通过。
  • 需要你做的:超过 3,000 行,需您批准,并在您定的窗口冻结 protocol.ts 后落地;本 PR 保持草稿。

Clause-②: no
Metadata-protocol: none

What changes

  • scripts/codemod-split-protocol.mjs, three additions over the module form PR refactor(metadata-protocol): generate the protocol.ts section split with a deterministic codemod #29998 had before:
    • The facade layer (lift, ruling 6624345766). Each section module's functions become the static methods of one stateless facet class, <Module>Facet (protocol-save.ts → ProtocolSaveFacet). A method still takes the host as its first parameter, so its body is the E1 body re-indented back to the class's depth. A facet holds no state: every field stays on the class and is read through the host. Each stub on the class calls <Facet>.<member>(…).
    • Docblock ownership. A private member's docblock moves verbatim onto its facet method, and the class keeps one line: /** Docblock and body: {@link <Facet>.<member>} in \` [Codemod split: generate the rest of the metadata-protocol protocol.ts split with a deterministic script, verified byte-identical #29985 codemod]. */. A public member's docblock stays on the facade, as the ruling requires, so the published .d.ts` and consumers' hover text are unchanged.
    • Private-static-through-host. A private static whose body reads nothing of the class (no this, super, arguments or class name) moves to its section's facet with no host parameter. The class keeps the delegation, so moved instance bodies still reach it as host.<static>.
    • The pilot's no-yield rule (a body no longer than its stub stays) is dropped: under the facade rule the class keeps delegations only.
    • The proof of the layer is its inverse (unlift). --verify reads the head alone, turns it back into the module form, and refuses anything the lift would not have written: a public docblock moved, a stub calling another facet, a facet member that is not a static method, a body line the lift did not indent. It then runs every earlier proof on that module form, host-split.mjs's checkMove included.
    • --self-test: 55 cases in 7 pinned batteries (a new facade layer battery of 10).
  • The generated split is --write on main eee41490, unedited. Its modules are protocol-class-head.ts, protocol-discovery-and-bases.ts, protocol-data-crud.ts (new), protocol-search-and-cache.ts, protocol-write-gates.ts, protocol-registry-hydration.ts, protocol-save.ts, protocol-history-and-publish.ts, protocol-package-commits.ts and protocol-diff-delete-load.ts. Nothing re-exports them.
  • Readers widened (ruling 6597555122 point 3) to the facet form:
    • register-item, lookup-artifact-item and view-expansion-reconcile census headers accept a facet's static member.
    • read-verb-canonical-fold follows <Facet>.<fn> stubs.
    • The receive-path test reads applyRemoteMetadataMutation as a static method.
    • Two newly moved members are re-keyed: isShippedByAnotherPackage gets a file row. anotherPackageShips is read as its delegation plus its facet body.
  • scripts/check-durability-degradation-log-level.mjs: a <X>Facet.fn(protocolHost(this), …) stub, or a private static's <X>Facet.fn(…), is followed into the facet's static method. Without that, 3 read seams left the census (102 → 99). There are three new self-test rows, and the read-seam floor goes 70 → 73.

Verification

Reading Base eee41490 Head
protocol.ts lines 23,135 13,647
Byte-identity 85/85 bodies; checkMove 78/78 instance (statics compared directly); the unlift, then the undo, rebuilds both base files
Determinism output tree sha256 85bbdf726e8fbb750229627f744bb152aa79fcfa02d95b939885bbdf3430fe92 on two runs; regeneration reproduces the committed tree
metadata-protocol typecheck exit 0 exit 0
metadata-protocol test 312 files, 22,346 passed, 26 skipped 312 files, 22,347 passed, 26 skipped
check:durability-log-level 51 seams (9 propagating), 102 read seams (13 discriminated) the same, and the output is byte-identical
core / objectql / spec test:repo, pnpm lint, pnpm build core 94, objectql 5, spec 1,539 passed; lint exit 0; build exit 0
dispatch-gates --commands (73) 73 derived, 73 run, every exit 0; --ran: 0 NOT-MEASURED, 0 UNRUN

.d.ts: ruling 6624345766 item 1, "public surface EQUAL"

The reading is dts-public.mjs, in the project files at issue-29985/dts-public.mjs, sha256 211b32062250606e658acd7fb8880e0079b4043121fb7b970c658b4b57bf5020. It is dtsnorm (6441032442 B) with one step added first: every private class member, together with its docblock, is cut from both texts. Everything else must then be equal statement for statement, which is stricter than item 1.

Reading index.d.ts index.d.cts
raw differs (private docblocks, and one union order) differs (the same)
private members cut, union order, second normalisation equal equal
declared surface equal equal
  • The control that edits one public docblock is caught.
  • The control that drops one private member passes.
  • The facet classes do not appear in the .d.ts.
  • 6441032442 B as written reads DIFFER, from the private docblocks only. That is what the ruling allows.

Facade reading (the #30019 gate's starting allow-list)

  • 85 members delegate to 10 facets:

    Facet Members Docblocks moved
    ClassHead 17 7
    DiscoveryAndBases 12 6
    DataCrud 1 1
    SearchAndCache 1 0
    WriteGates 18 17
    RegistryHydration 14 14
    Save 4 3
    HistoryAndPublish 5 3
    PackageCommits 9 2
    DiffDeleteLoad 4 1
  • 190 hand-series delegations in their own forms: .call(host, …) 162, plain 12, protocolHost(this) 16. Their docblocks stay on the class, because their modules are the hand series' and are not facets.

  • 26 methods with a body stay as named exceptions, plus the constructor (15 lines) and the static block (the host factory, 203 lines):

    • inferred-return-type (a public type the .d.ts would re-infer): getDiscovery 460 lines, findData 751, getData 75, cloneData 105, and saveMetaItem, publishMetaItem, rollbackMetaItem, deleteMetaItem at 7 lines each.
    • closed-set (outside E1's rewrite set): publishPackageDrafts 993 lines (a bare this), reconcileViewExpansions 53, packagelessOverlayWriteRefusal 14, namedBaseOverlayWriteRefusal 12.
    • static-reads-class: mergesOverlayAtRead, registryAllowsOverlay, isOverlayAllowed, isRuntimeCreateAllowed, codeOnlySourceHint, codeOnlyCreateError, codeOnlyOverrideError, refuseOrganizationScopedWrite, overlayPackageMismatchError.
    • no-host-read: rethrowUnlessMetadataStoreUnprovisioned, triggerAutomation.
    • name-taken: objectExtensionItemRefusal.
    • host-entry-predicate: packagedBaseRefusal.
    • static-shape (public static): resetEnvWritableCache.

Ablations

  • The facade battery's public-docblock case goes red when unlift's public-docblock check is removed.
  • The durability gate's two facet rows go red when the facet branch is removed, and the census drops to 99.
  • The facade battery also checks that a one-character edit in a moved docblock, a stub calling another facet, an extra facet member and an unindented body line each fail.

Findings outside this PR

protocol-packageless-overlay-migration.ts is #30000; it is unchanged here.

🤖 Generated with Claude Code

https://claude.ai/code/session_01WGufzgddMBbTQd9yVo4Yuz

…ith a deterministic codemod (WIP)

scripts/codemod-split-protocol.mjs applies the E1 host
split (packages/metadata-protocol/scripts/host-split.mjs) to whole
sections of ObjectStackProtocolImplementation and proves the move:
undoing it rebuilds the base protocol.ts and protocol-host.ts byte for
byte, and host-split's own check passes for every moved member.
Generated output, unedited.

Claude-Session: https://claude.ai/code/session_01WGufzgddMBbTQd9yVo4Yuz
Co-Authored-By: Claude <noreply@anthropic.com>
…e import prune

The codemod now drops the imports only the moved bodies read (the package
compiles with noUnusedLocals), and its byte-identity proof recomputes that
prune from the head's own references. A JSDoc {@link X} counts as a read, as
the compiler counts it. A move that would reach a type-predicate method
through a new host entry stays (the forwarding closure drops the predicate).
An inferred return type no longer keeps a method in protocol.ts.

Regenerated: 70/284 methods move into 10 section modules; protocol.ts goes
from 23135 to 13905 lines.

Claude-Session: https://claude.ai/code/session_01WGufzgddMBbTQd9yVo4Yuz
Co-Authored-By: Claude <noreply@anthropic.com>
…er in protocol.ts

A moved body that reads no member would never read its `host` parameter,
which the package's noUnusedParameters refuses, so such a method now stays
(stay rule no-host-read). Regenerated: 69/284 methods move; protocol.ts goes
from 23135 to 13915 lines.

Claude-Session: https://claude.ai/code/session_01WGufzgddMBbTQd9yVo4Yuz
Co-Authored-By: Claude <noreply@anthropic.com>
… in protocol.ts and widen the by-path censuses

The declaration bar on the built .d.ts refused the previous split: moving
getDiscovery's body made the compiler re-infer its printed return type
through the moved function, which dropped the @deprecated docblock on one
key. The pilot's rule is back: a non-private method with an inferred return
type stays. Regenerated: 61/284 methods move into 9 section modules;
protocol.ts goes from 23135 to 15318 lines.

The censuses that read protocol.ts by path now also read the modules the
split moved bodies to, where the class is spelled host. instead of this.
(ruling 6597555122): the recovery-door emit and projector counts, the
write-through and registerItem caller sets, the lookupArtifactItem sites,
the read-verb fold population (the signature is read off the class member,
the fold off the moved body) and the stored-row write census of the view
expansion reconcile.

Claude-Session: https://claude.ai/code/session_01WGufzgddMBbTQd9yVo4Yuz
Co-Authored-By: Claude <noreply@anthropic.com>
…verned bodies

The split moved the bodies the protocol.ts entry's invariants govern:
applyObjectRegistryMutation and hydrateOverlayIntoRegistry into
protocol-registry-hydration.ts, loadMetaFromDb into
protocol-diff-delete-load.ts, saveMetaItem's view storage into
protocol-save.ts. Each module gets an entry carrying the host entry's own
invariant paragraphs for the ADRs it now cites (ADR-0029 and ADR-0126,
ADR-0029, ADR-0005 and ADR-0087), as the sql-driver pilot did; the host
entry stays for the stubs and the citations left there.

Claude-Session: https://claude.ai/code/session_01WGufzgddMBbTQd9yVo4Yuz
Co-Authored-By: Claude <noreply@anthropic.com>
…lity census; parse through ts-parse

check-durability-degradation-log-level read three moved catch blocks as
inventing an answer, lost one read seam, and kept two stale
FAILURE_PROPAGATION_SITES keys, all because the codemod split moved the
bodies into section modules:

- A moved body that delegates its catch to the class's guard as
  host.rethrowUnlessMetadataStoreUnprovisioned(error) now reaches that
  guard through the same HOST CALL rule isReadCall already follows.
- A host.x() call resolves to the class member x first, through its stub,
  even when the module declares x itself (x's own moved body, whose extra
  host parameter failed the arity check). This restores
  rollbackToPackageCommit's seam around revertCommit.
- migrateStoredMetadata and duplicatePackage are keyed by their new
  modules.

The census reads the base's numbers again: 51 durability seams (9
propagating), 102 read seams (13 discriminated). Three self-test rows pin
the two rules (the read-seam floor goes 67 -> 70); removing either rule
turns exactly its row red.

The codemod now parses through ts-parse.mjs (parseSourceFile,
createProgramChecked), as check:parse-guard requires.

Claude-Session: https://claude.ai/code/session_01WGufzgddMBbTQd9yVo4Yuz
Co-Authored-By: Claude <noreply@anthropic.com>
@objectstack-fleet objectstack-fleet Bot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Nov 4, 2026
@github-actions

github-actions Bot commented Nov 4, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/metadata-protocol, touching 456 documentable anchor(s).

75 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 63b86b385dd2cbfeba131438112c49be2c74df91.

⛔ 18 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 1 anchor(s) matched too much of the corpus to be a work list: sys_user (literal, 49 pages)
  • the SDK route bridge reached 61 of 219 client-bound route-ledger rows — the other 158 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 158: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 103 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 10 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 63b86b385dd2cbfeba131438112c49be2c74df91 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 4d3ca895e003361790ddd7450a82b320371da644 — the merge of head bba9d24d79853f4ae24a0991d6faba94aa06a02c into base 63b86b385dd2cbfeba131438112c49be2c74df91, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 4d3ca895e003361790ddd7450a82b320371da644 && git checkout 4d3ca895e003361790ddd7450a82b320371da644
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 63b86b385dd2cbfeba131438112c49be2c74df91 bba9d24d79853f4ae24a0991d6faba94aa06a02c && git checkout -B drift-repro 63b86b385dd2cbfeba131438112c49be2c74df91 && git merge --no-ff bba9d24d79853f4ae24a0991d6faba94aa06a02c

node scripts/docs-audit/affected-docs.mjs --json 63b86b385dd2cbfeba131438112c49be2c74df91

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 63b86b385dd2cbfeba131438112c49be2c74df91 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

Regenerate the split on the latest main; main has not changed protocol.ts or protocol-host.ts since 9049be2.

Claude-Session: https://claude.ai/code/session_01WGufzgddMBbTQd9yVo4Yuz
Co-Authored-By: Claude <noreply@anthropic.com>
… form

Lift the codemod's output into the facade form of ruling 6624345766:
each section module becomes one stateless facet class whose static
methods take the host, every moved member on ObjectStackProtocolImplementation
delegates to its facet in one line, a private member's docblock moves
with its body, and pure private statics move too. The pilot's no-yield
rule is dropped (the facade keeps delegations only).

verify unlifts the head back to the module form and proves it against
the base as before. The by-path readers and the durability census
follow the facet form.

Claude-Session: https://claude.ai/code/session_01WGufzgddMBbTQd9yVo4Yuz
Co-Authored-By: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

domain:server#6 · os-support-ai · session_01WGufzibcN9oHa8kDbTTnL5

席位核对

Verdict: ACCEPT at head bba9d24d79853f4ae24a0991d6faba94aa06a02c · 2026-11-04T10:12Z

  • Shape: a deterministic codemod (facade form, ruling 6624345766). 85 ProtocolHandler members now each delegate in one line to 10 stateless facet classes (static methods, host first; 7 of them are pure private statics). protocol.ts goes from 23,135 to 13,647 lines. 54 private docblocks moved with their bodies, and public docblocks stay on the facade. The 190 hand-series delegations are unchanged. 26 methods, the constructor and the static block stay on the class as named exceptions for tooling: a structural gate that refuses any method body in a facade file once protocol.ts and engine.ts land in facade form (ruling 6624258595) #30019; the full list with reasons is in the dev report.
  • Proofs (dev report 6626233635):
    • Byte-identity 85/85, and verify unlifts the head and rebuilds base protocol.ts and protocol-host.ts byte for byte.
    • The output tree sha is identical on two runs.
    • The public .d.ts surface is EQUAL (issue-29985/dts-public.mjs), with both controls behaving.
    • typecheck 0. metadata-protocol has 22,347 tests, which is base plus one non-vacuity row.
    • test:repo: core 94, objectql 5, spec 1,539. The durability census is byte-identical to base.
    • lint and build exit 0. dispatch-gates 73/73, and --ran shows 0 NOT-MEASURED.
  • Declaration: Clause-②: no, Metadata-protocol: none, no runtime change. Reviewed as consistent.
  • Window: the maintainer approved in the project chat at 10:11Z. No open PR touches protocol.ts, and main has not touched metadata-protocol source this PR moves since base eee41490. CI at head: 35 success, 17 skipped, 0 failed.
  • Accepted deviations: the harness-pinned branch. The follow-up splits (157 private hand-series stubs with about 4,017 docblock lines, and stub compression) are filed after merge, at the maintainer's word. The hot bodies go to refactor: move shared state out of the protocol.ts and engine.ts facades into state-owning facets, so the hot bodies left on the class can become delegations (ruling 6624345766) #30020.
  • Landing: the seat sends pr_ready + automerge_enable through the relay. The tooling: a structural gate that keeps a registered facade class delegation-only #30030 gate lands after both facades.

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review November 4, 2026 10:13
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge November 4, 2026 10:13
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Nov 4, 2026
Merged via the queue into main with commit 48914fd Nov 4, 2026
53 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/project-thread-w94e86 branch November 4, 2026 10:40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/xl skip-changeset PR has no user-facing published change; bypasses the changeset gate tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants